package x.trident.core.db.api.util;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import x.trident.core.db.api.pojo.optRecord.OptRecordItemVO;
import x.trident.core.db.api.pojo.optRecord.OptRecordVO;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 操作记录工具类
 *
 * @author jason.yan
 * @date 2022/6/13
 */
public class OptRecordUtil {

    /**
     * 默认表主键
     */
    private static final String DEFAULT_TABLE_KEY = "Id";

    /**
     * 获得操作记录
     *
     * @param sourceObj 原始对象
     * @param newObj    新的对象
     * @param tableKey  主键名称
     * @return OptRecordVO
     */
    public static <T> OptRecordVO getOptRecord(T sourceObj, T newObj, String tableKey) {
        Long primaryKey = (Long) ReflectUtil.getFieldValue(sourceObj, tableKey);
        List<OptRecordItemVO> items = compareObject(sourceObj, newObj);
        return new OptRecordVO(primaryKey, items);
    }

    /**
     * 获得操作记录，使用默认主键名称"Id"
     *
     * @param sourceObj 原始对象
     * @param newObj    新的对象
     * @return OptRecordVO
     */
    public static <T> OptRecordVO getOptRecord(T sourceObj, T newObj) {
        return getOptRecord(sourceObj, newObj, DEFAULT_TABLE_KEY);
    }

    /**
     * 获得List中所有对象的修改记录
     *
     * @param sourceList 原始对象集合
     * @param newList    新对象集合
     * @param tableKey   主键名称
     * @return List<OptRecordVO>
     */
    public static <T> List<OptRecordVO> getOptRecordForList(List<T> sourceList, List<T> newList, String tableKey) {
        Map<Object, T> sourceMap = sourceList.stream().collect(Collectors.toMap(t -> ReflectUtil.getFieldValue(t, tableKey), t -> t));
        Map<Object, T> newMap = newList.stream().collect(Collectors.toMap(t -> ReflectUtil.getFieldValue(t, tableKey), t -> t));
        List<OptRecordVO> optRecords = new ArrayList<>();
        sourceMap.forEach((key, value) -> {
            Object nObj = newMap.get(key);
            if (ObjectUtil.isNotNull(value) && ObjectUtil.isNotNull(nObj)) {
                optRecords.add(getOptRecord(value, nObj, tableKey));
            }
        });
        return optRecords;
    }

    /**
     * 获得List中所有对象的修改记录，使用默认主键名称"Id"
     *
     * @param sourceList 原始对象集合
     * @param newList    新对象集合
     * @return List<OptRecordVO>
     */
    public static <T> List<OptRecordVO> getOptRecordForList(List<T> sourceList, List<T> newList) {
        return getOptRecordForList(sourceList, newList, DEFAULT_TABLE_KEY);
    }

    /**
     * 比较对象，获得属性存在差异的部分
     * 只能比较基本类型属性。
     *
     * @param sourceObj 原始对象
     * @param newObj    新的对象
     * @return 操作记录明细集合
     */
    private static <T> List<OptRecordItemVO> compareObject(T sourceObj, T newObj) {
        // 处理原始对象 sourceObject
        Map<String, Object> sourceMap = objToMap(sourceObj);

        // 处理新对象 newObject
        Map<String, Object> newMap = objToMap(newObj);

        // 对比原始map、新map，按照差异生成操作记录明细
        List<OptRecordItemVO> recordItems = new ArrayList<>();
        newMap.forEach((key, nValue) -> {
            if (ObjectUtil.isBasicType(nValue) || nValue instanceof String) {
                Object sValue = sourceMap.get(key);
                if (!ObjectUtil.equal(nValue, sValue)) {
                    recordItems.add(new OptRecordItemVO(key, sValue, nValue));
                }
            }
        });
        return recordItems;
    }

    /**
     * Object 转化 Map
     * 过滤掉包含 TableField(exist = false) 注释的属性
     *
     * @param obj 待处理对象
     * @return Map<String, Object> key=属性名，value=属性值
     */
    private static <T> Map<String, Object> objToMap(T obj) {
        Map<String, Object> retMap = new HashMap<>();
        BeanUtil.beanToMap(obj, retMap,
                CopyOptions.create().setPropertiesFilter((field, o) -> {
                    TableField tableField = field.getAnnotation(TableField.class);
                    return ObjectUtil.isNull(tableField) || tableField.exist();
                }));
        return retMap;
    }
}
